/**
 * @filename Matrix.h
 * @brief Matrix class from which two vector classes derived.
 * @author Sun Zehui <sunzehui18@outlook.com>
 * @version 1.0.0
 * @date 2021-10-06
 */

#ifndef __MATRIX_HEADER__
#define __MATRIX_HEADER__

#include<iostream>
#include<assert.h>
using namespace std;


/**
 * @brief deepcopy 
 *
 * @tparam T 
 * @param T type array
 * @param T type array
 * @param int row number
 * @param int col number
 */
template<class T>
void deepcopy(T**, T**, int, int);

template<class T> class Matrix;

template<class T>
ostream& operator<< (ostream& out, const Matrix<T>& t);



/**
 * @brief Matrix Class
 *
 * @tparam T
 */
template <class T>
class Matrix{
public:
	~Matrix();
	/**
	 * @brief Matrix Constructor
	 *
	 * @param r row number
	 * @param c col number
	 * @param array containing the elements
	 */
	Matrix(int r, int c, T* array);
	bool is_square();
	
	/**
	 * @brief operator overload * operator to realize the ordinary multiplication
	 * between matrices
	 *
	 * @param m Matrix<T>&
	 *
	 * @return Matrix<T> ONLY FOR RIGHT VALUE
	 */
	const Matrix<T> operator*(const Matrix<T>& m) const;
       	void print();

	/**
	 * @brief Matrix Copy constructor
	 *
	 * @param Matrix
	 */
	Matrix(const Matrix&);
	int getRow();
	int getCol();
	T** getEle();
	
	/**
	 * @brief value 
	 *
	 * @return return a scalar value of single matrix
	 */
	T& value();
	void describe();
	/**
	 * @brief operator<< overload to display the elements
	 *
	 * @param out ostream&
	 * @param t Matrix<T>
	 *
	 * @return ostream&
	 */
	friend ostream& operator<< <>(ostream& out, const Matrix<T>& t);
private:
	int n_row;
	int n_col;
	T** matrix_elements;
};

template<class T>
class ColVector;


/**
 * @brief RowVector Class 
 *
 * @tparam T
 */
template <class T>
class RowVector: public Matrix<T>{
public:
	~RowVector();
	RowVector(int r, T* array);
};


/**
 * @brief ColVector Class
 *
 * @tparam T
 */
template<class T>
class ColVector: public Matrix<T>{
public:
	~ColVector();
	ColVector(int c, T* array);
};


/**
 * @brief deepcopy, to copy one matrix's elements to another matrix not by pointer. 
 *
 * @tparam T
 * @param copy accept copied's values
 * @param copied offer values for copy
 * @param r matrix number of row
 * @param c matrix number of column
 */
template<class T>
void deepcopy(T** copy, T** copied, int r, int c){
        for(int i=0; i<r; ++i)
                for(int j=0; j<c; ++j)
                        copy[i][j] = copied[i][j];
}


/**
 * @brief describe function, to describe one matrix's row number, column number and contents for convenience.
 *
 * @tparam T
 */
template<class T>
void Matrix<T>::describe(){
	cout << "Matrix row numbers: " << n_row << ", " << "Matrix column numbers: " << n_col << endl;
	cout <<	"Matrix Contents are: "<< endl;
	cout << *this;
}


/**
 * @brief operator<< overload ostream operator
 *
 * @tparam T
 * @param out ostream object
 * @param t Matrix<T> object
 *
 * @return ostream object
 */
template<class T>
ostream& operator<< (ostream& out, const Matrix<T>& t){
	for(int i=0; i<t.n_row; ++i){
		for(int j=0; j<t.n_col; ++j){
			out << t.matrix_elements[i][j] << '\t';
                }
                out << endl;
        }
        return out;
}


/**
 * @brief value return single value for 1*1 matrix
 *
 * @tparam T
 *
 * @return type T value of 1*1 matrix
 */
template<class T>
T& Matrix<T>::value(){
	assert(n_col == 1 && n_row == 1);
	return matrix_elements[0][0];
}

template<class T>
int Matrix<T>::getRow(){
        return n_row;
}

template<class T>
int Matrix<T>::getCol(){
        return n_col;
}

template <class T>
T** Matrix<T>::getEle(){
        return matrix_elements;
}


/**
 * @brief RowVector Initialization for RowVector
 *
 * @tparam T
 * @param r number of elements in row vector
 * @param 1 default number for n_row
 * @param array value array filling the vectors
 */
template<class T>
RowVector<T>::RowVector(int r, T* array):Matrix<T>(1, r, array){}

template<class T>
ColVector<T>::ColVector(int c, T* array):Matrix<T>(c, 1, array){}

template<class T>
RowVector<T>::~RowVector(){}

template<class T>
ColVector<T>::~ColVector(){}


/**
 * @brief Matrix Initializtion of Matrix
 *
 * @tparam T
 * @param r number of row
 * @param c number of column
 * @param a value array filling the matrices
 */
template<class T>
Matrix<T>::Matrix(int r, int c, T* a):n_row(r), n_col(c){
        matrix_elements = new T*[r];
        for(int i=0; i<r; i++){
                matrix_elements[i] = new T[c];
        }
        for(int i=0; i<r; ++i){
                for(int j=0; j<c; ++j){
                        matrix_elements[i][j] = a[c*i+j];
                }
        }

}


/**
 * @brief Matrix Copy Constructor
 *
 * @tparam T
 * @param m Matrix<T> offered
 */
template<class T>
Matrix<T>::Matrix(const Matrix<T>& m):n_row(m.n_row), n_col(m.n_col){
        matrix_elements = new T*[n_row];
        for(int i=0; i<n_row; i++){
                matrix_elements[i] = new T[n_col];
        }
        deepcopy(matrix_elements, m.matrix_elements, n_row, n_col);
}


/**
 * @brief ~Matrix delete and cycle the memory allocated
 *
 * @tparam T
 */
template<class T>
Matrix<T>::~Matrix(){
        for(int i=0; i<n_row; ++i){
                delete[] matrix_elements[i];
        }
        delete[] matrix_elements;
}


/**
 * @brief is_square whether the matrix is square or not
 *
 * @tparam T
 *
 * @return bool
 */
template<class T>
bool Matrix<T>::is_square(){
        return n_row == n_col;
}


/**
 * @brief operator* overload * operator to realize mutiplication between matrices and vectors
 * matrix<T> * matrix<T> = matrix<T> 
 * (Vector<T> *) matrix<T> * Vector<T> = matrix<T>
 * sameVector<T> * sameVector<T> = InnerProduct
 * ColVector<T> * RowVector<T> = matrix<T>
 * RowVector<T> * ColVector<T> = matrix<T> NOT T !!!! Plz use value() member function to extract
 * single value stored in 1*1 matrix!
 *
 * @tparam T
 * @param m matrix 
 *
 * @return Matrix<T> in different shapes!
 */
template<class T>
const Matrix<T> Matrix<T>::operator*(const Matrix<T>& m) const{
        if(n_col == m.n_col && n_row == m.n_row && (n_col == 1 || n_row==1)){
		T temp[1]{};
		for(int i=0; i<n_row; ++i){
			for(int j=0; j<n_col; ++j){
				temp[0] += matrix_elements[i][j]*m.matrix_elements[i][j];
			}
		}
		Matrix<T> res(1,1,temp);
		return res;
	}
        T temp[n_row*m.n_col]{};
        for(int i=0; i<n_row; ++i){
                for(int j=0; j<m.n_col; ++j){
                        for(int k=0; k<n_col; ++k){
                                temp[m.n_col*i+j]+=matrix_elements[i][k]*m.matrix_elements[k][j];
                        }
                }
        }
        Matrix<T> res(n_row, m.n_col, temp);
        return res;
}

#else
/// DO NOTHING
#endif 

